Skip to content

[6.x] Improve type handling in GraphQL schemas#14677

Open
lostgeek wants to merge 10 commits into
statamic:6.xfrom
lostgeek:feat/graphql-type-patch
Open

[6.x] Improve type handling in GraphQL schemas#14677
lostgeek wants to merge 10 commits into
statamic:6.xfrom
lostgeek:feat/graphql-type-patch

Conversation

@lostgeek
Copy link
Copy Markdown
Contributor

@lostgeek lostgeek commented May 14, 2026

Improved GraphQL Types for Entries, Terms, and Assets

This PR tries to tackle the same issue that #14618 tried. So similarly, if you want to approach this topic in a different way, feel free to close this until v7. In any case, we will likely be patching these changes into our Statamic projects, so feel free to approach me for more feedback in the future, if you close this.

Closes #12974.

Summary

This PR introduces concrete type resolution and dynamic union types for the Entries, Terms, and Assets GraphQL fieldtypes. Instead of always returning the generic interface type (e.g. EntryInterface, TermInterface, AssetInterface), fieldtypes now resolve to the most specific type possible based on their blueprint configuration, and dynamic union types are generated if multiple blueprints are possible (e.g. DynamicEntryUnionType).

It also adds per-collection and per-taxonomy top-level queries that return typed results, so consumers can query e.g. blogPosts { ... } of type [Entry_BlogPosts_Article] instead of entries(collection: ["blog_posts"]) { ... } of type [EntryInterface], and fetch a single item via e.g. blogPost(id: "...") instead of entry(collection: "blog_posts", id: "...").

Both features are controlled through a new improved_types config section in config/graphql.php.

What changed

Configstatamic.graphql.improved_types is now a structured array:

  • enabled (default true, env STATAMIC_GRAPHQL_IMPROVED_TYPES) — controls fieldtype improved type resolution.
  • collections — list of collection handles (or '*' for all) that get dedicated typed queries.
  • terms — list of taxonomy handles (or '*' for all) that get dedicated typed queries.

Entries fieldtype — When improved_types.enabled is true:

  • No configured collections in blueprint → returns EntryInterface (unchanged).
  • Single collection with one blueprint → returns the concrete EntryType (e.g. Entry_BlogPosts_Article).
  • Multiple blueprints across one or more collections → returns a dynamically generated DynamicEntryUnionType scoped to exactly those blueprint types.
  • Lists use [Type!] (non-null items) instead of [Type].

Terms fieldtype — Same logic as Entries, using TermType / DynamicTermUnionType.

Assets fieldtype — When improved_types.enabled is true, resolves to the concrete AssetType for the configured container instead of AssetInterface. Lists use non-null items.

Per-collection queries — For each collection listed in improved_types.collections, two dedicated queries are registered (e.g. blogPosts and blogPost for blog_posts). Only registered if the collections resource is also enabled.

  • SpecificEntriesQuery — paginated list; camelCased collection handle as query name.
  • SpecificEntryQuery — single entry; singular camelCased handle (appends Entry on name collision, e.g. blogblogEntry).
  • List query accepts the same args as entries minus collection.
  • Single query accepts the same args as entry minus collection.
  • Both return EntryType or DynamicEntryUnionType depending on blueprint count.

Per-taxonomy queries — Same pattern for taxonomies listed in improved_types.terms (e.g. tags and tag).

New classes:

  • DynamicEntryUnionType — A union type composed of the concrete EntryTypes matching the fieldtype's configured collections/blueprints.
  • DynamicTermUnionType — Same concept for taxonomy terms.
  • SpecificEntriesQuery — Per-collection paginated query with typed results.
  • SpecificEntryQuery — Per-collection single-entry query with typed results.
  • SpecificTermsQuery — Per-taxonomy paginated query with typed results.
  • SpecificTermQuery — Per-taxonomy single-term query with typed results.

Why

The generic interface types make it difficult for GraphQL consumers to use the returned data types without a further cleanup step, rendering the advantage of GraphQL somewhat limited. One of the most common pain points is with an Entries fieldtype that only allows a single blueprint still delivering an EntryInterface type, making it difficult to use the data easily in a client application.

The per-collection/taxonomy queries go further by giving each collection or taxonomy its own top-level queries with properly typed results, eliminating the need to filter by __typename afterwards.

Possible improvements

  • The naming of the dynamic union types is not ideal. Currently it prefixes DynamicEntryUnionType_ and then concatenates the blueprint handles with underscores, which can lead to very long type names such as DynamicEntryUnionType_Entry_BlogPosts_Article_Entry_BlogPosts_News.

@lostgeek lostgeek changed the title Improve type handling in GraphQL schemas [6.x] Improve type handling in GraphQL schemas May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent resolvement of gql types

1 participant